﻿/*
 * http://odetocode.com/Articles/97.aspx
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace AspCompiler
{
    public class CmdProcessor
    {
        private bool _executing;
        private Process _process;
        private StreamReader _standardOutput, _standardError;
        private byte[] _errorBuffer = new byte[512], _outputBuffer = new byte[512];
        private AsyncCallback _outputReady, _errorReady;
        private AsyncState _outputState, _errorState;
        private List<Command> _commands = new List<Command>();

        public void QueueCommand(string command, string arguments)
        {
            _commands.Add(new Command(command, arguments));
        }

        public void Execute()
        {
            if (_commands.Count == 0)
            {
                Finished(this, new EventArgs());
                return;
            }

            _process = InitializeProcess(_commands[0]);
            _executing = true;
            _process.Start();

            AttachStreams();
            PrepareAsyncState();

            _standardOutput.BaseStream.BeginRead(_outputBuffer, 0, _outputBuffer.Length, _outputReady, _outputState);
            _standardError.BaseStream.BeginRead(_errorBuffer, 0, _errorBuffer.Length, _errorReady, _errorState);
        }

        public void Cancel()
        {
            _process.Kill();
        }

        public event CmdProcessorEventHandler TextReceived;

        public event EventHandler Finished;

        private ProcessStartInfo GetStartInfo(Command command)
        {
            ProcessStartInfo psi = new ProcessStartInfo();
            psi.FileName = command.command;
            psi.Arguments = command.arguments;
            psi.UseShellExecute = false;
            psi.RedirectStandardError = true;
            psi.RedirectStandardOutput = true;
            psi.CreateNoWindow = true;

            return psi;
        }

        private Process InitializeProcess(Command command)
        {
            if (_executing)
                throw new ApplicationException("Another process is currently executing");

            _process = new Process();
            _process.StartInfo = GetStartInfo(command);
            _process.EnableRaisingEvents = true;
            _process.Exited += new EventHandler(_process_Exited);

            return _process;
        }

        private void _process_Exited(object sender, EventArgs e)
        {
            int exitCode = _process.ExitCode;

            string n = Environment.NewLine;
            TextReceived(this, new CmdProcesserEventArgs("--- Exit Code " + _process.ExitCode + " ---" + n + n));

            _process.Dispose();
            _process = null;
            _executing = false;

            if (exitCode != 0)
                _commands.Clear();
            else
                _commands.RemoveAt(0);

            Execute();
        }

        private void AttachStreams()
        {
            _standardOutput = _process.StandardOutput;
            _standardError = _process.StandardError;
        }
        
        private void OutputCallback(IAsyncResult ar)
        {
            AsyncState state = ar.AsyncState as AsyncState;

            int count = state.Stream.BaseStream.EndRead(ar);

            if (count > 0)
            {
                string text = System.Text.Encoding.ASCII.GetString(state.Buffer, 0, count);
                TextReceived(this, new CmdProcesserEventArgs(text));

                state.Stream.BaseStream.BeginRead(state.Buffer, 0, state.Buffer.Length, _outputReady, state);
            }
        }

        private void PrepareAsyncState()
        {
            _outputReady = new AsyncCallback(OutputCallback);
            _outputState = new AsyncState(_standardOutput, _outputBuffer);
            _errorReady = new AsyncCallback(OutputCallback);
            _errorState = new AsyncState(_standardError, _errorBuffer);
        }

        public class AsyncState
        {
            protected StreamReader _stream;
            protected byte[] _buffer;

            public AsyncState(StreamReader stream, byte[] buffer)
            {
                _stream = stream;
                _buffer = buffer;
            }

            public StreamReader Stream
            {
                get { return _stream; }
            }

            public byte[] Buffer
            {
                get { return _buffer; }
            }
        }

        private class Command
        {
            public Command(string command, string arguments)
            {
                this.command = command;
                this.arguments = arguments;
            }

            public string command { get; set; }
            public string arguments { get; set; }
        }
    }

    public delegate void CmdProcessorEventHandler(object sender, CmdProcesserEventArgs e);

    public class CmdProcesserEventArgs
    {
        public CmdProcesserEventArgs(string text)
        {
            Output = text;
        }

        public string Output { get; set; }
    }
}
